﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.ComponentModel;

namespace SmallGameArith {
    #region case model
    public enum BrickType {
        None = 0, I, L, Z, O, T, S, J
    }
    public enum BrickDirect {
        N = 0, E, W, S
    }
    public static class Ex {
        public static BrickDirect GetNext(this BrickDirect instance) {
            var v = (int)instance;
            var v2 = v + 1;
            var v3 = v2 % 4;
            return (BrickDirect)v3;
        }
        public static BrickDirect GetPrevious(this BrickDirect instance) {
            var v = (int)instance;
            var v2 = v == 0 ? 3 : v - 1;
            return (BrickDirect)v2;
        }
    }
    public class Brick {
        Location[] GetCheckCells(Location[] old,Location[] newcells) {
            var ns = new List<Location>();
            foreach (var n in newcells) {
                foreach (var o in old) {
                    if (n == o) {
                        goto skip;
                    }
                }
                ns.Add(n);
                skip: ;
            }
            return ns.ToArray();
        }
        public bool MoveDown() {
            var oldcells = this.QueryCells();
            this.fixedPoint.row++;
            var newcells = this.QueryCells();
            var b = this.CanAct(GetCheckCells(oldcells,newcells));
            if (b) {
                this.OnUpdate(oldcells,newcells);
            } else {
                this.IsFixed = true;
            }
            return b;
        }
        public void MoveLeft() {
            var oldcells = this.QueryCells();
            this.fixedPoint.column--;
            var newcells = this.QueryCells();
            var b = this.CanAct(GetCheckCells(oldcells,newcells));
            if (b) {
                this.OnUpdate(oldcells,newcells);
            } else {
                this.fixedPoint.column++;
            }
        }
        public void MoveRight() {
            var oldcells = this.QueryCells();
            this.fixedPoint.column++;
            var newcells = this.QueryCells();
            var b = this.CanAct(GetCheckCells(oldcells,newcells));
            if (b) {
                this.OnUpdate(oldcells,newcells);
            } else {
                this.fixedPoint.column--;
            }
        }
        public void Eddy() {
            var oldcells = this.QueryCells();
            this.brickDirect = this.brickDirect.GetNext();
            var newcells = this.QueryCells();
            var b = this.CanAct(GetCheckCells(oldcells,newcells));
            if (b) {
                this.OnUpdate(oldcells,newcells);
            } else {
                this.brickDirect = this.brickDirect.GetPrevious();
            }
        }
        /// <summary>
        /// task c001 
        /// 完成者签名:
        /// 业务说明：让一个砖块直接下落到底部
        /// </summary>
        public void ToBottom() {            
            var x = true;
            var old = this.QueryCells();
            dynamic nc=null;
            while (x) {
                this.fixedPoint.row++;
                nc = this.QueryCells();
                x = this.CanAct(this.GetCheckCells(old,nc));
            }
            this.fixedPoint.row--;
            nc = this.QueryCells();
            this.Update(old,nc);
        }
        public event Action<Location[],Location[]> Update;
        protected virtual void OnUpdate(Location[] oldcells,Location[] newcells) {
            if (this.Update != null && !this.IsFixed) {
                this.Update.Invoke(oldcells,newcells);
            }
        }
        //
        public event Func<Location[],bool> CanAct;
        public BrickType brickType;
        public Location fixedPoint;
        public BrickDirect brickDirect;
        Location[] QueryCells() {
            var cells = new Location[4];
            switch (this.brickType) {
                case BrickType.None:
                break;
                case BrickType.I:
                switch (this.brickDirect) {
                    case BrickDirect.N://横I
                    cells[0] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row + 2,this.fixedPoint.column);
                    break;
                    case BrickDirect.E://竖I
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column + 2);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row + 2,this.fixedPoint.column);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column + 2);
                    break;
                    default:
                    break;
                }
                break;
                case BrickType.L:
                switch (this.brickDirect) {
                    case BrickDirect.N://L
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column - 2);
                    break;
                    case BrickDirect.E:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row + 2,this.fixedPoint.column);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column + 2);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column - 2);
                    break;
                    default:
                    break;
                }
                break;
                case BrickType.Z:
                switch (this.brickDirect) {
                    case BrickDirect.N:
                    cells[0] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.E:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column + 1);
                    break;
                    default:
                    break;
                }
                break;
                case BrickType.O:
                switch (this.brickDirect) {
                    case BrickDirect.N:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.E:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    default:
                    break;
                }
                break;
                case BrickType.T:
                switch (this.brickDirect) {
                    case BrickDirect.N:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    break;
                    case BrickDirect.E:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[3] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    break;
                    default:
                    break;
                }
                break;
                case BrickType.S:
                switch (this.brickDirect) {
                    case BrickDirect.N:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.E:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[2] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    cells[3] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    cells[2] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column + 1);
                    break;
                    default:
                    break;
                }
                break;
                case BrickType.J:
                switch (this.brickDirect) {
                    case BrickDirect.N:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column - 2);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    break;
                    case BrickDirect.E:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row + 2,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column - 1);
                    break;
                    case BrickDirect.W:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row + 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row,this.fixedPoint.column + 2);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    break;
                    case BrickDirect.S:
                    cells[0] = new Location(this.fixedPoint.row,this.fixedPoint.column);
                    cells[1] = new Location(this.fixedPoint.row - 1,this.fixedPoint.column);
                    cells[2] = new Location(this.fixedPoint.row - 2,this.fixedPoint.column);
                    cells[3] = new Location(this.fixedPoint.row,this.fixedPoint.column + 1);
                    break;
                    default:
                    break;
                }
                break;
                default:
                break;
            }
            return cells;
        }
        public bool IsFixed { get; set; }
        public Brick() {
        }
    }
    public struct Location {
        public static bool operator ==(Location a,Location b) {
            if (a.row == b.row && a.column == b.column) {
                return true;
            } else {
                return false;
            }
        }
        public static bool operator !=(Location a,Location b) {
            if (a.row == b.row && a.column == b.column) {
                return false;
            } else {
                return true;
            }
        }
        public Location(int x,int y) {
            this.row = x; this.column = y;
        }
        public int row; public int column;
    }
    public class Container {
        BrickType[][] brickPool;
        public const int width = 11;
        public const int height = 25;
        public const int viewoffset = 4;
        Random random;
        public TetrisInfo pTetrisInfo { get; set; }
        public Brick current;
        ICanvas canvas;
        public double GetBrickDownSpeed() { return 600d; }
        public Container(ICanvas c) {
            if (c == null) {
                throw new Exception("技术框架支持不能为空");
            }
            this.canvas = c;
            this.canvas.eOpenBrickDownEngine += canvas_eOpenBrickDownEngine;
            this.brickPool = new BrickType[height][];
            for (int i = 0 ; i < height ; i++) {
                this.brickPool[i] = new BrickType[width];
            }
            InitialBrickPool();
            this.current = new Brick();
            this.current.Update += current_Update;
            this.current.CanAct += current_CanAct;
            this.random = new Random();
            this.pTetrisInfo = new TetrisInfo();
        }

        private void InitialBrickPool() {
            for (int i = 0 ; i < height ; i++) {
                for (int j = 0 ; j < width ; j++) {
                    this.brickPool[i][j] = BrickType.None;
                }
            }
        }

        bool current_CanAct(Location[] newcells) {
            var r = true;
            foreach (var item in newcells) {
                if (item.row < 0 || item.row >= Container.height || item.column < 0 || item.column >= Container.width || this.brickPool[item.row][item.column] != BrickType.None) {
                    r = false; break;
                }
            }
            return r;
        }

        public void OpenMainEngine() {
            InitialBrickPool();
            OpenGenerateBrickEngine();
        }

        private void OpenGenerateBrickEngine() {
            this.CurrentBrickRefresh();
            this.canvas.OpenBrickDownEngine();
        }

        private void CurrentBrickRefresh() {
            this.current.brickType = (BrickType)this.random.Next(1,8);
            this.current.brickDirect = (BrickDirect)this.random.Next(0,4);
            this.current.fixedPoint = new Location { row = 3,column = 4 };
            this.current.IsFixed = false;
        }

        void canvas_eOpenBrickDownEngine() {
            var b = this.current.MoveDown();
            if (!b) {
                this.canvas.CloseBrickDownEngine();
                this.CheckFloor();
                this.CheckGameOver();
            }
        }

        private void CheckGameOver() {
            foreach (var item in this.brickPool[viewoffset]) {
                if (item != BrickType.None) {
                    this.canvas.GameOver();
                    goto over;
                }
            }
            this.OpenGenerateBrickEngine();
            over: ;
        }
        int GetMaxUnNullNo() {
            var i = 0;
            var count = height - viewoffset;
            var x = viewoffset - 1;
            for (int j = x ; j < count ; j++) {
                foreach (var item in this.brickPool[j]) {
                    if (item != BrickType.None) {
                        goto end;
                    }
                }
                i++;
            }
            end:
            return i;
        }
        private void CheckFloor() {
            var mark = GetMaxUnNullNo();
            for (int i = mark ; i < height ; i++) {
                foreach (var item in this.brickPool[i]) {
                    if (item == BrickType.None) {
                        goto next;
                    }
                }
                //var j = i;
                //while (j > mark) {
                //    brickPool[j] = brickPool[j - 1];
                //    j--;
                //}
                var bp = this.brickPool.ToList();
                bp.Remove(this.brickPool[i]);
                bp.Insert(0,GetRow());
                this.brickPool = bp.ToArray();
                this.pTetrisInfo.CutFloorNumber++;
                next: ;
            }
        }
        BrickType[] GetRow() {
            var bts = new BrickType[width];
            for (int i = 0 ; i < width ; i++) {
                bts[i] = BrickType.None;
            }
            return bts;
        }
        void current_Update(Location[] oldcells,Location[] newcells) {
            foreach (var item in oldcells) {
                this.brickPool[item.row][item.column] = BrickType.None;
            }
            foreach (var item in newcells) {
                this.brickPool[item.row][item.column] = this.current.brickType;
            }
            this.canvas.Draw(this.brickPool.Skip(viewoffset).ToArray());
        }

        #region INotifyPropertyChanged 成员

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
    public class TetrisInfo:NotifyBase {
        int cutFloor;
        public int CutFloorNumber {
            get { return cutFloor; }
            set { cutFloor = value; base.OnPropertyChanged("CutFloorNumber"); }
        }
        string tv;
        public string TimeValue {
            get { return tv; }
            set { tv = value; base.OnPropertyChanged("TimeValue"); }
        }
    }
    #endregion
    #region sl技术支持
    public interface ICanvas {
        void Draw(BrickType[][] brickpool);
        void OpenBrickDownEngine();
        void CloseBrickDownEngine();
        void GameOver();
        event Action eOpenBrickDownEngine;
    }
    #endregion
}
